/** @file    DesktopNotifierBundle.cpp
 *  @author  Alessandro Polo
 *  @version $Id: DesktopNotifierBundle.cpp 2873 2010-08-08 19:06:41Z alex $
 *  @brief
 * File containing methods for the wosh::services::DesktopNotifierBundle class.
 * The header for this class can be found in DesktopNotifierBundle.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "DesktopNotifierBundle.h"

 #include "DesktopNotifierQObject.h"

 #include <core/ObjectAllocator.h>
 #include <core/Fact.h>
 #include <core/MessageProcessingCommon.h>
 #include <core/Table.h>
 #include <core/List.h>

 #include <core/UserManager.h>
 #include <core/DeviceManager.h>
 #include <core/BusManager.h>
 #include <core/SystemError.h>
 #include <core/WoshKernel.h>

 #include <framework/communication/CommunicationEvents.h>

// #include "FormPopup.h"
// #include "FormSysTray.h"

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef _OS_WIN32
# include <core/win32/desktop.h>
#else
# include <core/posix/desktop.h>
#endif
//////////////////////////////////////////////////////////////////////////////////////////////////////////////


using namespace std;
namespace wosh {
 namespace services {

 WOSH_REGISTER(wosh::services::DesktopNotifierBundle, "wosh::BundleGeneric", _DesktopNotifier_VERSION, _static_DesktopNotifierBundle1 )
 WOSH_REGISTER(wosh::services::DesktopNotifierBundle, "wosh::interfaces::services::CommunicationManager", _DesktopNotifier_VERSION, _static_DesktopNotifierBundle2 )
 WOSH_REGISTER(wosh::services::DesktopNotifierBundle, "wosh::interfaces::services::DesktopNotifier", _DesktopNotifier_VERSION, _static_DesktopNotifierBundle3 )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

DesktopNotifierBundle::DesktopNotifierBundle()
	: BundleGeneric() {
	BundleGeneric::setName( _DesktopNotifier_NAME, false );
	Properties.update( _KEY_Version, _DesktopNotifier_VERSION );

	this->trayObject = new DesktopNotifierQObject();
	this->trayObject->setListener(this);
	this->trayObject->setMouseMoveAccurancy(15);

	this->notificationsMaxSize = 15;

	this->devState = wosh::interfaces::Device::UNKNOWN;

///////////////////////////////////////////////////////////////////////////////////////////////
//TEST:
this->credentials = new wosh::security::SecurityTokenProviderOS();
this->credentials->setAuthenticationMode(wosh::SecurityToken::AUTH_PASSWORD, "SASL");
//!TEST:
///////////////////////////////////////////////////////////////////////////////////////////////

	Log(LOG_DEBUG, " Setting default properties and permissions.." );
	Properties.set( _CommunicatorService_KEY_ChannelType,	(int)Notification::TYPE_TEXTUAL + (int)Notification::TYPE_DATA, Permission(Permission::Read) );
	Properties.set( _CommunicatorService_KEY_ChannelLink,	(int)Communicator::LINK_SYNCH, Permission(Permission::Read) );
	Properties.set( _CommunicatorService_KEY_Cost,			0.0,Permission::RW_R__R__() );
	Properties.set( _CommunicatorService_KEY_Priority,		20,	Permission::RW_RW_R__() );
	Properties.set( _CommunicatorService_KEY_Reliability,	50,	Permission::RW_RW_R__() );
	Properties.set( _CommunicatorService_KEY_Protocol,		(int)Notification::PROTOCOL_SHELL + (int)Notification::PROTOCOL_HUMAN,	Permission::RW_R__R__() );

	Properties.set( _DesktopNotifier_KEY_UserLoggedID,		this->account.getUserID(), Permission(Permission::Read) );
	Properties.set( _DesktopNotifier_KEY_UserLogged,		this->account.getUser(), Permission(Permission::Read) );
	Properties.set( _DesktopNotifier_KEY_UserLoggedTs,		this->account.getLastSeen(), Permission(Permission::Read) );

	Properties.set( _DesktopNotifier_KEY_DoNotDisturb,		false, Permission::RW_RW_R__() );
	Properties.set( _DesktopNotifier_KEY_CacheSize,			this->notificationsMaxSize, Permission::RW_RW_R__() );
	Properties.set( _DesktopNotifier_KEY_MouseMoveAccurancy,15, Permission::RW_RW_R__() );

	Properties.set( _Device_KEY_DeviceStatus, wosh::interfaces::Device::getDeviceStateAsString(wosh::interfaces::Device::UNKNOWN), Permission( Permission::Read ) );
	Properties.monitor(_DesktopNotifier_KEY_DoNotDisturb);
	Properties.monitor(_DesktopNotifier_KEY_CacheSize);
	Properties.monitor(_DesktopNotifier_KEY_MouseMoveAccurancy);


	Log(LOG_DEBUG, " Registering methods.." );
	MethodMessageResponse* mmForceUser = Methods.create<MethodMessageResponse>( _DesktopNotifier_METHOD_forceuser, "convert message", Permission::R_XR__R__() );
	mmForceUser->setMethod( this, (MethodMessageResponsePtr)&DesktopNotifierBundle::mmDoForceUser );
//	mmForceUser->getArgument(0).set(Data::STRING, false);

	MethodMessageResponse* mmSendMessageToUser = Methods.create<MethodMessageResponse>( _CommunicatorService_METHOD_sendmessage, "send message to (wosh) username", Permission(Permission::RX) );
	mmSendMessageToUser->setMethod( this, (MethodMessageResponsePtr)&DesktopNotifierBundle::mmDoSendMessageToUser );

	MethodList* mmListContacts = Methods.create<MethodList>( _METHOD_List, "List contacts", Permission(Permission::RX) );
	mmListContacts->setDataFieldExtractor( DataFieldExtractor<ContactInfo>::createFor< DataAdapterItem<ContactInfo> >(&this->account, "Contacts") );

	MethodList* mmListNotifications = Methods.create<MethodList>( _DesktopNotifier_METHOD_listnotif, "List notifications", Permission::R_XR__R__() );
	mmListNotifications->setDataFieldExtractor( DataFieldExtractor<Notification>::createFor< DataAdapterListT<Notification> >(this->notifications, "Notifications") );

	setBundleState(Bundle::STATE_CREATED, false);
 }


DesktopNotifierBundle::~DesktopNotifierBundle() {
	Log(LOG_INFO, " Destroying.." );
	if ( isBundleRunning() ) {
		Log(LOG_WARNING, "~DesktopNotifierBundle() : Destroying while RUNNING! Trying to stop.." );
		bundleStop();
	 }
	// release notifications
	this->notifications.free();
	delete this->trayObject; this->trayObject = NULL;

///////////////////////////////////////////////////////////////////////////////////////////////
//TEST:
delete this->credentials; this->credentials = NULL;
//!TEST:
///////////////////////////////////////////////////////////////////////////////////////////////

	Log(LOG_VERBOSE, ":~DesktopNotifierBundle() : Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL

WRESULT DesktopNotifierBundle::bundleStart()
 {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STARTING) )
		return WRET_ERR_WRONG_STATE;

	WRESULT ret = WRET_OK;

	BusDevices.setMessageHandler(this);
	WRESULT bus_connected_dev = BusDevices.connect( _Bus_Devices, Bus::ACTION_DEFERRED );
	if ( WFAILED(bus_connected_dev) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Connecting Bus ("_Bus_Devices")..", bus_connected_dev );

	WRESULT bus_connected_sec = BusSecurity.connect( _Bus_Security, Bus::ACTION_DEFERRED );
	if ( WFAILED(bus_connected_sec) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Connecting Bus ("_Bus_Security")..", bus_connected_sec );

	if ( WSUCCEEDED(ret) ) {
		Log(LOG_VERBOSE, ":bundleStart() : Registering as Device.." );
		wosh::Device* myself = new wosh::Device(*this);
		myself->getInterfaces() = this->Interfaces;
		WoshKernel::getInstance()->devices().registerDevice( myself );
	 }

	ret += DesktopNotifierBundle::updateUserLogged( true );

	Response* response = new Response( _METHOD_ListProperties, Properties.toTable(false) );
	Message* message = new Message(response);
	message->getDestination().fromString("*:Communication");
	signMessage(message);
	BusCore.postMessage(message);

	this->trayObject->monitor(true);

	setBundleState(Bundle::STATE_STARTED);
	return WRET_OK;
 }

WRESULT DesktopNotifierBundle::bundleStop()
 {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STOPPING) )
		return WRET_ERR_WRONG_STATE;

	WRESULT ret = WRET_OK;
	Properties.transactionBeginWrite();
	Properties.update( "UserLogged", "" );
	Properties.update( "UserLoggedTs", "" );
	Properties.update( "UserLoggedID", "" );
	Properties.transactionEnd();
	DesktopNotifierBundle::updateUserLogged( false );

	if ( WSUCCEEDED(ret) ) {
		Log(LOG_VERBOSE, ":bundleStart() : UnRegistering as Device.." );
		WoshKernel::getInstance()->devices().unRegisterDevice( this->getName() );
	 }
	this->trayObject->monitor(false);

	setBundleState(Bundle::STATE_STOPPED);
	return WRET_OK;
 }

////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES

void DesktopNotifierBundle::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;

	if ( message.getContent()->isNotification() ) {
		if ( message.getDestination().isBroadcast() )
			return;
		if ( !MessageFilter::isMessageTarget(message, this) )
			return;
		if ( !isBundleRunning() )
			return;
		const Notification* notif = message.getContent()->asNotification();
		WRESULT sent = notify( notif );
		Message* reply = message.replyTo( new Response( _CommunicatorService_RESPONSE_notif_sent, new Variant(notif->getID()), sent ) );
		signMessage(reply);
		BusCore.postMessage(reply);
		return;
	 }

	DeviceManager::busMessageForBundleDevices( message, source, this, this->Methods, this->Log, this->BusCore, this->BusDevices );
 }

//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT

bool DesktopNotifierBundle::updatingProperty( bool& do_update, const Variant& value_proposed, Property& property_current, const PropertiesProvider* source )
 {
	if ( property_current.getKey() == _DesktopNotifier_KEY_DoNotDisturb ) {
		return true;
	 }
	else if ( property_current.getKey() == _DesktopNotifier_KEY_CacheSize ) {
		this->notifications.transactionBeginWrite();
		this->notificationsMaxSize = value_proposed.toInteger(0);
		this->notifications.transactionEnd();
		return true;
	 }
	else if ( property_current.getKey() == _DesktopNotifier_KEY_MouseMoveAccurancy ) {
		this->trayObject->setMouseMoveAccurancy( value_proposed.toInteger(0) );
		return true;
	 }
	else // maybe is a (common) BundleGeneric property, let base implementation process it
		return BundleGeneric::updatingProperty(do_update, value_proposed, property_current, source);
 }

////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT DesktopNotifierBundle::updateUserLogged( bool online, bool do_raiseEvent  ) {
	if ( this->account.isUserSetAndSeen() ) {
		if ( Utilities::std_time() - this->account.getLastSeen() < 10 ) {
			Log(LOG_VERBOSE, ":updateUserLogged(%d) [%s=>%s] Skipped because of updated", online, this->account.getUserID().c_str(), this->account.getUser().c_str() );
			return WRET_OK;
		 }
	 }
	string syUser = "";
	bool userOk = getSystemUserName(syUser);
	this->account.setUserID(syUser);
	if ( !userOk ) {
		SystemError::raise(this, Log, SystemError::WARNING, 0, "updateUserLogged", "FAILED reading Windows User" );
		return WRET_ERR_INTERNAL;
	 }
	Log(LOG_VERBOSE, ":updateUserLogged(%d) Windows User %s", online, this->account.getUserID().c_str() );
	if ( online )
		this->account.setLastSeen();
	else
		this->account.setLastSeen(-Utilities::std_time());

	// recognize the user
	this->account.setUser( getUsernameFromAccountName(this->account.getUserID()) );
	Properties.transactionBeginWrite();
	Properties.update( "UserLoggedID", this->account.getUserID() );
	Properties.update( "UserLogged", this->account.getUser() );
	Properties.update( "UserLoggedTs", this->account.getLastSeen() );
	Properties.transactionEnd();
	if ( this->account.getUser().empty() ) {
		SystemError::raise(this, Log, SystemError::WARNING, 0, "updateUserLogged", "Account %s not recognized", this->account.getUserID().c_str() );
		return WRET_ERR_INTERNAL;
	 }
	Log(LOG_INFO, ":updateUserLogged(%d) %s recognized as %s", online, this->account.getUserID().c_str(), this->account.getUser().c_str() );

	if ( !do_raiseEvent )
		return WRET_OK;

	Fact* fact = NULL;
	if ( Utilities::std_time() - this->account.getLastSeen() < 60 ) {
		Log(LOG_VERBOSE, ":updateUserLogged(%d) [%s=>%s] Skipped ON/OFF EVENT", online, this->account.getUserID().c_str(), this->account.getUser().c_str() );
		fact = new UserInteraction_Event(this->account.getUser());
	 }
	else {
		if ( online )
			fact = new UserPresence_StateChanged(this->account.getUser(), User::PRESENCE_ONLINE );
		else
			fact = new UserPresence_StateChanged(this->account.getUser(), User::PRESENCE_OFFLINE_AWAY );
	 }
	Message* msg_event = new Message( fact );
	msg_event->setDestinationBroadcast(true);
	signMessage(msg_event);
	BusSecurity.postMessage(msg_event);
	return WRET_OK;
}

//////////////////////////////////////////////////////////////////////////////////////////// GENERATED EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////// COMMUNICATOR INTERFACE

bool DesktopNotifierBundle::isUserReachable( const string& wosh_username ) {
	if ( this->account.getUser() != wosh_username ) return false;
	if ( this->account.isTimedOut(60) ) return true;
	return false;
 }

WRESULT DesktopNotifierBundle::sendMessageToUser( const string& wosh_username, const string& message ) {
	if ( wosh_username.empty() ) {
		Log(LOG_WARNING, ":sendMessageToUser() Empty User, Sending anyway [current: %s]", this->account.getUser().c_str() );
		return showMessage(message);
	 }
	if ( wosh_username != this->account.getUser() ) {
		Log(LOG_CRITICAL, ":sendMessageToUser() FAILED : Wosh-User %s is not reachable! [current: %s]", wosh_username.c_str(), this->account.getUser().c_str() );
		return WRET_ERR_PARAM;
	 }
	return showMessage(message);
 }

WRESULT DesktopNotifierBundle::sendMessageTo( const string& custom_username, const string& message ) {
	if ( custom_username.empty() ) {
		Log(LOG_WARNING, ":sendMessageTo() FAILED Empty User [current: %s]", this->account.getUser().c_str() );
		return WRET_ERR_PARAM;
	 }
	if ( custom_username != this->account.getUserID() ) {
		Log(LOG_CRITICAL, ":sendMessageTo() FAILED : Win-User %s is not reachable! [current: %s]", custom_username.c_str(), this->account.getUserID().c_str() );
		return WRET_ERR_PARAM;
	 }
	return showMessage(message);
 }

WRESULT DesktopNotifierBundle::notify( const Notification* notification, const Message* ) {
	if ( notification == NULL ) return WRET_ERR_PARAM;

	if ( !notification->isForAnyone() && notification->getRecipent_User() != this->account.getUser() ) {
		Log(LOG_CRITICAL, ":notify() FAILED : Win-User %s is not reachable! [current: %s]", notification->getRecipent_User().c_str(), this->account.getUser().c_str() );
		return WRET_ERR_PARAM;
	 }

	this->notifications.transactionBeginWrite();
	if ( (int)this->notifications.size() > this->notificationsMaxSize ) {
		Notification* oldNot = this->notifications.pop(0);
		delete oldNot; oldNot = NULL;
	 }
	this->notifications.push_back( notification->clone() );
	this->notificationCurrent = this->notifications.size() - 1;
	this->notifications.transactionEnd();

	QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::NoIcon;
	long showTime = 10*1000; // ms
	switch( notification->getPriority() ) {
		case Notification::PRIORITY_REALTIME: {
				icon = QSystemTrayIcon::Critical; break;
				showTime = 120*1000;
			}
		case Notification::PRIORITY_HIGH: {
				icon = QSystemTrayIcon::Warning; break;
				showTime = 60*1000;
			}
		case Notification::PRIORITY_LOW: {
				icon = QSystemTrayIcon::Information; break;
				showTime = 30*1000;
			}
		case Notification::PRIORITY_DEFAULT: {
				icon = QSystemTrayIcon::Information; break;
				showTime = 15*1000;
			}
		default: break;
	 }
	string title = "by " + notification->getSender_User();
	if ( notification->isForAnyone() )
		title += " (for anyone)";

	if ( notification->getNotificationType() == Notification::TYPE_TEXTUAL ) {
		const TextMessage* txtMsg = dynamic_cast<const TextMessage*>(notification);
		if ( txtMsg != NULL && !txtMsg->getSubject().empty() )
			title = txtMsg->getSubject();
	 }

	this->trayObject->showTrayMessage( title, notification->getPlain(), showTime, icon );
	return WRET_OK;
 }

////////////////////////////////////////////////////////////////////////////////////// COMMUNICATOR INTERFACE
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////// CALLBACKS

void DesktopNotifierBundle::user_MouseNotMoved() {
	if ( this->account.getLastSeen() < 0 ) return;
	if ( Utilities::std_time() - this->account.getLastSeen() < 60 ) return;
	updateUserLogged( false, true );
 }

void DesktopNotifierBundle::user_MouseMoved() {
	if ( this->account.isUserSetAndSeen() ) {
		if ( Utilities::std_time() - this->account.getLastSeen() < 30 ) {
			Log(LOG_VERBOSE, ":updateUserLogged() [%s=>%s] Skipped because of updated", this->account.getUserID().c_str(), this->account.getUser().c_str() );
			return;
		 }
	 }
	else if ( this->account.getLastSeen() < 0 ) {
		// user was offline?
		updateUserLogged( true, true );
		return;
	 }
	this->account.setLastSeen();

	Message* msg_event = new Message( new UserInteraction_Event(this->account.getUser()) );
	msg_event->setDestinationBroadcast(true);
	signMessage(msg_event);
	BusSecurity.postMessage(msg_event);
 }

void DesktopNotifierBundle::user_TrayIconClicked( short ) {
	user_MouseMoved();
 }

void DesktopNotifierBundle::user_TrayMessageClicked() {
	 this->notifications.transactionBeginWrite();
	if ( this->notificationCurrent >= 0 && this->notificationCurrent < (int)this->notifications.size() )  {
		Notification* currNot = this->notifications.itemAt(this->notificationCurrent);
		if ( currNot != NULL ) {
			Response* response = new Response( _CommunicatorService_RESPONSE_notif_read, new Variant(currNot->getID()), WRET_OK );
			Message* msg_response = new Message( response );
			msg_response->setSource(this);
			msg_response->getDestination().fromString("*:Communication");
			this->credentials->signMessage( msg_response, this->account.getUser(), this->account.getUserID() );
			BusCore.postMessage(msg_response);
		 }
		this->notificationCurrent = -1;
		this->notifications.transactionEnd();
		updateUserLogged( true, true );
		return;
	 }
	this->notifications.transactionEnd();
 }

/////////////////////////////////////////////////////////////////////////////////////////////////// CALLBACKS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////// TRAY-ICON METHODS

WRESULT DesktopNotifierBundle::setTrayIcon( QSystemTrayIcon* tray_icon ) {
	this->trayObject->setTrayIcon(tray_icon);
	return WRET_OK;
 }


WRESULT DesktopNotifierBundle::showMessage( const string& message ) {
	this->trayObject->showTrayMessage( "", message.c_str(), 10000 );
	return WRET_OK;
 }

/////////////////////////////////////////////////////////////////////////////////////////// TRAY-ICON METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

std::string DesktopNotifierBundle::getUsernameFromAccountName( const string& accountname ) {
	std::vector<std::string> users = UserManager::findUserByProperty( "WIN_Username", Variant(accountname) );
	if ( users.size() == 0 ) {
		Log(LOG_WARNING, ":getUsernameFromAccountName() : User '%s' not recognized!", accountname.c_str() );
		return "";
	 }
	else if ( users.size() > 1 ) {
		Log(LOG_WARNING, ":getUsernameFromAccountName() : User '%s' not recognized!", accountname.c_str() );
		return "";
	 }
	return users[0];
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS

Response* DesktopNotifierBundle::mmDoForceUser( const Message* requestMessage ) {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	string userStr = request->getArgument().toString();
	Log(LOG_WARNING, ":mmDoForceUser(%s)", userStr.c_str() );

	this->account.set( "", userStr, Utilities::std_time() );
	Properties.transactionBeginWrite();
	Properties.update( "UserLoggedID", this->account.getUserID() );
	Properties.update( "UserLogged", this->account.getUser() );
	Properties.update( "UserLoggedTs", this->account.getLastSeen() );
	Properties.transactionEnd();

	return request->replyResponse(WRET_OK);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* DesktopNotifierBundle::mmDoSendMessageToUser( const Message* requestMessage ) {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	if ( !request->hasData() ) {
		return request->replyResponse(WRET_ERR_PARAM, "Invalid data");
	 }
	Variant dUserName;
	Variant dMessage;
	request->extractArguments(dUserName, dMessage);
	if ( !dUserName.isStringNotEmpty() || !dMessage.isStringNotEmpty() )
		return request->replyResponse(WRET_ERR_PARAM, "Invalid arguments [username, message]");

	std::string sender = "";
	if ( requestMessage->getSecurityToken() != NULL )
		sender = requestMessage->getSecurityToken()->getUsername();

	Log(LOG_INFO, "mmDoSendMessageToUser(%s) : %d chars", dUserName.asString().c_str(), dMessage.asString().size() );

	if ( sender != dMessage.asString() && sender != "wosh ")
		dMessage.asString() += " [by " + sender + "]";

	WRESULT ret = sendMessageToUser( dUserName.asString(), dMessage.asString() );
	if ( WSUCCEEDED(ret) )
		return request->replyResponse(WRET_OK, "Sent!");
	return request->replyResponse(ret, "Internal Error!");
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace services
}; // namespace wosh
